﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace FySystem.Caches.SimpleCaches
{
    public class SimpleCache<T> : ICache<T>
    {
        private Func<string, T> _getRule;
        private Action<string, T> _setRule;
        private Func<string, T, T> _setBeforeRule;
        private Action<string, T> _deleteRule;

        private Dictionary<string, CacheData<T>> _caches = new Dictionary<string, CacheData<T>>();

        /// <summary>
        /// 失效时间，<=0表示永不失效
        /// </summary>
        public long ExpireSeconds { get; set; } = 0;

        /// <summary>
        /// 设置获取数据规则
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="fun"></param>
        public void SetGetRule(Func<string, T> fun)
        {
            _getRule = fun;
        }

        /// <summary>
        /// 设置写入数据规则（用于在写入缓存前对对象进行一定的处理）
        /// </summary>
        /// <param name="fun"></param>
        public void SetBeforeSetDataRule(Func<string, T, T> fun)
        {
            _setBeforeRule = fun;
        }

        /// <summary>
        /// 获取插入数据规则
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="fun"></param>
        public void SetSetDataRule(Action<string, T> fun)
        {
            _setRule = fun;
        }

        /// <summary>
        /// 获取删除数据规则
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="fun"></param>
        public void SetDeleteRule(Action<string, T> fun)
        {
            _deleteRule = fun;
        }

        /// <summary>
        /// 获取缓存中的数据
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public T this[string key]
        {
            get
            {
                T res = default(T);
                try
                {
                    CacheData<T> data = GetData(key);
                    if (data != null)
                        res = data.Data;
                }
                catch (Exception)
                {
                    throw;
                }
                return res;
            }
        }

        /// <summary>
        /// 清空缓存中的所有数据，根据传入的数据重新初始化缓存
        /// </summary>
        /// <param name="datas"></param>
        /// <returns></returns>
        public bool ResetCacheData(Dictionary<string, T> datas)
        {
            bool res = false;
            try
            {
                lock (_caches)
                {
                    _caches.Clear();

                    if (datas != null)
                    {
                        foreach (string key in datas.Keys)
                        {
                            _caches.Add(key, new CacheData<T>()
                            {
                                CreateTime = DateTime.Now,
                                Data = datas[key]
                            });
                        }
                    }
                }
                res = true;
            }
            catch (Exception)
            {
                throw;
            }
            return res;
        }

        /// <summary>
        /// 向缓存中插入或者更新数据
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="updateDataSource">是否同时更新数据源，通常是更新数据库</param>
        /// <returns></returns>
        public bool Set(string key, T value, bool updateDataSource = true)
        {
            try
            {
                //执行前置回调
                if (_setBeforeRule != null && updateDataSource == true)
                    value = _setBeforeRule.Invoke(key, value);

                lock (_caches)
                {
                    if (_caches.ContainsKey(key))
                        _caches[key].SetData(value);
                    else
                        _caches.Add(key, new CacheData<T>()
                        {
                            CreateTime = DateTime.Now,
                            Data = value
                        });
                }

                //更新到数据源
                if (updateDataSource == true)
                    _setRule?.Invoke(key, value);
            }
            catch (Exception)
            {
                throw;
            }
            return true;
        }

        /// <summary>
        /// 删除缓存中的数据
        /// </summary>
        /// <param name="key"></param>
        /// <returns>缓存中删除成功返回true，缓存中没有数据返回false</returns>
        public bool Delete(string key)
        {
            bool res = false;
            try
            {
                //先从缓存中删除
                T data = InvalidateItem(key);

                //从数据源中删除
                _deleteRule?.Invoke(key, data);
            }
            catch (Exception)
            {
                throw;
            }
            return res;
        }

        /// <summary>
        /// 判断缓存中数据是否存在，只要存在，即便超时也返回true
        /// </summary>
        /// <param name="key"></param>
        /// <param name="searchFromDataSource">如果缓存中不存在，是否还从数据源中查找</param>
        /// <returns></returns>
        public bool IsInCache(string key, bool searchFromDataSource)
        {
            bool res = false;
            try
            {
                lock (_caches)
                {
                    if (_caches.ContainsKey(key))
                    {
                        res = true;
                    }
                }
                if (res == false && searchFromDataSource == true)
                {
                    if (_getRule != null)
                    {
                        T data = _getRule.Invoke(key);
                        if (data != null)
                            res = true;
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return res;
        }

        /// <summary>
        /// 强制让某个缓存过期（不会执行DeleteRule）
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public T InvalidateItem(string key)
        {
            T res = default(T);
            try
            {
                lock (_caches)
                {
                    if (_caches.ContainsKey(key))
                    {
                        res = _caches[key].Data;
                        _caches.Remove(key);
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return res;
        }

        /// <summary>
        /// 强制让所有缓存失效（删除所有）
        /// </summary>
        /// <returns></returns>
        public bool InvalidateAll()
        {
            try
            {
                lock (_caches)
                {
                    _caches.Clear();
                }
            }
            catch (Exception)
            {
                throw;
            }
            return true;
        }

        /*
         * =====================================私有函数====================================
         * */
        private CacheData<T> GetData(string key)
        {
            CacheData<T> data = null;
            try
            {
                lock (_caches)
                {
                    if (_caches.ContainsKey(key))
                    {
                        if (ExpireSeconds > 0)
                        {
                            //判断数据是否过期
                            if ((DateTime.Now - _caches[key].CreateTime).TotalSeconds <= ExpireSeconds)
                                data = _caches[key];
                        }
                        else
                            data = _caches[key];
                    }

                    //如果没查到数据，再从数据源中查询，这里有个取舍问题，如果这部分代码放在lock中，如果涉及到查询数据库，就会很慢，造成别的操作也卡死，
                    //如果放在lock外面，这条缓存在从数据库中读取的过程中如果被设置为无效了，那从数据库中读取后依然会成为有效的
                    if (data == null && _getRule != null)
                    {
                        //如果设置Get数据获取规则，则再从数据获取规则中查询数据
                        T dataT = _getRule.Invoke(key);

                        data = new CacheData<T>()
                        {
                            CreateTime = DateTime.Now,
                            Data = dataT
                        };

                        //再将数据放到缓存里
                        if (_caches.ContainsKey(key))
                            _caches[key] = data;
                        else
                            _caches.Add(key, data);
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return data;
        }
    }
}
